﻿using System;
using System.Collections;
using GeoAPI.Geometries;
using NetTopologySuite.Geometries;

namespace NetTopologySuite.Algorithm
{
	public class PointLocator
	{
		public PointLocator()
		{
		}

		public PointLocator(IBoundaryNodeRule boundaryRule)
		{
			if (boundaryRule == null)
			{
				throw new ArgumentException("Rule must be non-null");
			}
			this._boundaryRule = boundaryRule;
		}

		public bool Intersects(Coordinate p, IGeometry geom)
		{
			return this.Locate(p, geom) != Location.Exterior;
		}

		public Location Locate(Coordinate p, IGeometry geom)
		{
			if (geom.IsEmpty)
			{
				return Location.Exterior;
			}
			if (geom is ILineString)
			{
				return PointLocator.Locate(p, (ILineString)geom);
			}
			if (geom is IPolygon)
			{
				return this.Locate(p, (IPolygon)geom);
			}
			this._isIn = false;
			this._numBoundaries = 0;
			this.ComputeLocation(p, geom);
			if (this._boundaryRule.IsInBoundary(this._numBoundaries))
			{
				return Location.Boundary;
			}
			if (this._numBoundaries > 0 || this._isIn)
			{
				return Location.Interior;
			}
			return Location.Exterior;
		}

		private void ComputeLocation(Coordinate p, IGeometry geom)
		{
			if (geom is IPoint)
			{
				this.UpdateLocationInfo(PointLocator.Locate(p, (IPoint)geom));
			}
			if (geom is ILineString)
			{
				this.UpdateLocationInfo(PointLocator.Locate(p, (ILineString)geom));
				return;
			}
			if (geom is Polygon)
			{
				this.UpdateLocationInfo(this.Locate(p, (IPolygon)geom));
				return;
			}
			if (geom is IMultiLineString)
			{
				foreach (ILineString l in ((IMultiLineString)geom).Geometries)
				{
					this.UpdateLocationInfo(PointLocator.Locate(p, l));
				}
				return;
			}
			if (geom is IMultiPolygon)
			{
				foreach (IPolygon poly in ((IMultiPolygon)geom).Geometries)
				{
					this.UpdateLocationInfo(this.Locate(p, poly));
				}
				return;
			}
			if (geom is IGeometryCollection)
			{
				IEnumerator enumerator = new GeometryCollectionEnumerator((IGeometryCollection)geom);
				while (enumerator.MoveNext())
				{
					object obj = enumerator.Current;
					IGeometry geometry = (IGeometry)obj;
					if (geometry != geom)
					{
						this.ComputeLocation(p, geometry);
					}
				}
			}
		}

		private void UpdateLocationInfo(Location loc)
		{
			if (loc == Location.Interior)
			{
				this._isIn = true;
			}
			if (loc == Location.Boundary)
			{
				this._numBoundaries++;
			}
		}

		private static Location Locate(Coordinate p, IPoint pt)
		{
			if (pt.Coordinate.Equals2D(p))
			{
				return Location.Interior;
			}
			return Location.Exterior;
		}

		private static Location Locate(Coordinate p, ILineString l)
		{
			if (!l.EnvelopeInternal.Intersects(p))
			{
				return Location.Exterior;
			}
			Coordinate[] coordinates = l.Coordinates;
			if (!l.IsClosed && (p.Equals(coordinates[0]) || p.Equals(coordinates[coordinates.Length - 1])))
			{
				return Location.Boundary;
			}
			if (CGAlgorithms.IsOnLine(p, coordinates))
			{
				return Location.Interior;
			}
			return Location.Exterior;
		}

		private static Location LocateInPolygonRing(Coordinate p, ILinearRing ring)
		{
			if (!ring.EnvelopeInternal.Intersects(p))
			{
				return Location.Exterior;
			}
			return CGAlgorithms.LocatePointInRing(p, ring.CoordinateSequence);
		}

		private Location Locate(Coordinate p, IPolygon poly)
		{
			if (poly.IsEmpty)
			{
				return Location.Exterior;
			}
			ILinearRing shell = poly.Shell;
			Location location = PointLocator.LocateInPolygonRing(p, shell);
			if (location == Location.Exterior)
			{
				return Location.Exterior;
			}
			if (location == Location.Boundary)
			{
				return Location.Boundary;
			}
			foreach (ILinearRing ring in poly.InteriorRings)
			{
				Location location2 = PointLocator.LocateInPolygonRing(p, ring);
				if (location2 == Location.Interior)
				{
					return Location.Exterior;
				}
				if (location2 == Location.Boundary)
				{
					return Location.Boundary;
				}
			}
			return Location.Interior;
		}

		private readonly IBoundaryNodeRule _boundaryRule = BoundaryNodeRules.EndpointBoundaryRule;

		private bool _isIn;

		private int _numBoundaries;
	}
}
